#include "JSEncapsulate.h"
#include "ustring.h"

#include "jsobj.h"
#include "jsutil.h"

namespace UOX
{
	void JSEncapsulate::InternalReset( void )
	{
		beenParsed[JSOT_INT]	= false;
		beenParsed[JSOT_DOUBLE] = false;
		beenParsed[JSOT_BOOL]	= false;
		beenParsed[JSOT_STRING] = false;
		beenParsed[JSOT_OBJECT] = false;
		nativeType				= JSOT_COUNT;
		className				= "Native";
		classCached				= false;
		intVal					= 0;
		floatVal				= 0.0f;
		boolVal					= false;
		stringVal				= "";
		objectVal				= NULL;
	}
	JSEncapsulate::JSEncapsulate() : cx( NULL ), vp( NULL ), obj( NULL )
	{
		InternalReset();
	}
	void JSEncapsulate::SetContext( JSContext *jsCX, jsval *jsVP )
	{
		cx = jsCX;
		vp = jsVP;
		Init();
	}
	void JSEncapsulate::Init( void )
	{
		if( JSVAL_IS_PRIMITIVE( *vp ) )
		{
			if( JSVAL_IS_DOUBLE( (*vp) ) )
				nativeType	= JSOT_DOUBLE;
			else if( JSVAL_IS_INT( (*vp) ) )
				nativeType	= JSOT_INT;
			else if( JSVAL_IS_BOOLEAN( (*vp) ) )
				nativeType	= JSOT_BOOL;
			else if( JSVAL_IS_STRING( (*vp) ) )
				nativeType	= JSOT_STRING;
			else if( JSVAL_IS_VOID( (*vp) ) )
				nativeType	= JSOT_VOID;
			else if( JSVAL_IS_NULL( (*vp) ) )
				nativeType	= JSOT_NULL;
		}
		else if( JSVAL_IS_OBJECT( (*vp) ) )
			nativeType	= JSOT_OBJECT;
	}
	JSEncapsulate::JSEncapsulate( JSContext *jsCX, jsval *jsVP ) : cx( jsCX ), vp( jsVP ), obj( NULL )
	{
		InternalReset();
		Init();
	}
	JSEncapsulate::JSEncapsulate( JSContext *jsCX, JSObject *jsVP ) : intVal( 0 ), floatVal( 0 ), boolVal( false ), stringVal( "" ), objectVal( NULL ), cx( jsCX ), vp( NULL ), obj( jsVP )
	{
		InternalReset();
		// We don't want to call Init() here, because we *know* it's an Object
		nativeType				= JSOT_OBJECT;
		objectVal				= (void*)JS_GetPrivate( cx, jsVP );
		beenParsed[JSOT_OBJECT]	= true;
	}

	bool JSEncapsulate::isType( JSEncapsObjectType toCheck )
	{
		return( nativeType == toCheck );
	}

	int JSEncapsulate::toInt( void )
	{
		if( nativeType == JSOT_OBJECT )
			throw new std::runtime_error( "Cannot convert JS Object to an int" );
		if( !beenParsed[JSOT_INT] )	
			Parse( JSOT_INT );
		return intVal;
	}
	bool JSEncapsulate::toBool( void )
	{
		if( nativeType == JSOT_OBJECT )
			throw new std::runtime_error( "Cannot convert JS Object to a bool" );
		if( !beenParsed[JSOT_BOOL] )	
			Parse( JSOT_BOOL );
		return boolVal;
	}
	float JSEncapsulate::toFloat( void )
	{
		if( nativeType == JSOT_OBJECT )
			throw new std::runtime_error( "Cannot convert JS Object to a float" );
		if( !beenParsed[JSOT_DOUBLE] )	
			Parse( JSOT_DOUBLE );
		return floatVal;
	}
	std::string JSEncapsulate::toString( void )
	{
		if( nativeType == JSOT_OBJECT )
			throw new std::runtime_error( "Cannot convert JS Object to a string" );
		if( !beenParsed[JSOT_STRING] )	
			Parse( JSOT_STRING );
		return stringVal;
	}
	void *JSEncapsulate::toObject( void )
	{
		if( nativeType != JSOT_OBJECT )
			throw new std::runtime_error( "Cannot convert to JS Object" );
		if( !beenParsed[JSOT_OBJECT] )	
			Parse( JSOT_OBJECT );
		return objectVal;
	}

	std::string JSEncapsulate::ClassName( void )
	{
		std::string rVal = className;
		if( !classCached )
		{
			if( nativeType == JSOT_OBJECT )
			{
				JSObject *obj2 = NULL;
				if( vp != NULL )
					obj2 = JSVAL_TO_OBJECT( *vp );
				else
					obj2 = obj;
				if( obj2 != NULL )
				{
					JSClass *mClass = OBJ_GET_CLASS( cx, obj2 );
					if( mClass->flags & JSCLASS_IS_EXTENDED )	// extended class
					{
						JSExtendedClass *mClass2	= (JSExtendedClass *)mClass;
						className					= mClass2->base.name;
					}
					else
					{
						className = mClass->name;
					}
					rVal = className;
				}
			}
			classCached = true;
		}
		return rVal;
	}
	void JSEncapsulate::Parse( JSEncapsObjectType typeConvert )
	{
		jsdouble	fvalue;
		int			ivalue;
		UString		svalue;
		bool		bvalue;
		switch( typeConvert )
		{
		case JSOT_INT:
			switch( nativeType )
			{
			case JSOT_INT:		intVal = JSVAL_TO_INT( (*vp) );	break;
			case JSOT_DOUBLE:
								JS_ValueToNumber( cx, (*vp), &fvalue );
								intVal = (int)fvalue;
								break;
			case JSOT_BOOL:		intVal = ( (JSVAL_TO_BOOLEAN( (*vp) ) == JS_TRUE) ? 1 : 0 );	break;
			case JSOT_STRING:
								svalue = JS_GetStringBytes( JS_ValueToString( cx, *vp ) );
								intVal = svalue.toInt();
								break;
			default:
			case JSOT_COUNT:
				break;
			}
			break;
		case JSOT_DOUBLE:
			switch( nativeType )
			{
			case JSOT_INT:		
								ivalue		= JSVAL_TO_INT( (*vp) );
								floatVal	= (float)ivalue;
								break;
			case JSOT_DOUBLE:
								JS_ValueToNumber( cx, (*vp), &fvalue );
								floatVal	= (float)fvalue;
								break;
			case JSOT_BOOL:		floatVal	= ( (JSVAL_TO_BOOLEAN( (*vp) ) == JS_TRUE) ? 1.0f : 0.0f );	break;
			case JSOT_STRING:
								svalue		= JS_GetStringBytes( JS_ValueToString( cx, *vp ) );
								floatVal	= svalue.toFloat();
								break;
			default:
			case JSOT_COUNT:
				break;
			}
			break;
		case JSOT_BOOL:
			switch( nativeType )
			{
			case JSOT_INT:		
								ivalue	= JSVAL_TO_INT( (*vp) );
								boolVal	= (ivalue != 0);
								break;
			case JSOT_DOUBLE:
								JS_ValueToNumber( cx, (*vp), &fvalue );
								boolVal	= (fvalue != 0.0f);
								break;
			case JSOT_BOOL:		boolVal = (JSVAL_TO_BOOLEAN( (*vp) ) == JS_TRUE);	break;
			case JSOT_STRING:
								svalue	= JS_GetStringBytes( JS_ValueToString( cx, *vp ) );
								boolVal = (svalue.upper() == "TRUE");
								break;
			default:
			case JSOT_COUNT:
				break;
			}
			break;
		case JSOT_STRING:
			switch( nativeType )
			{
			case JSOT_INT:		
								ivalue		= JSVAL_TO_INT( (*vp) );
								stringVal	= UString::number( ivalue );
								break;
			case JSOT_DOUBLE:
								JS_ValueToNumber( cx, (*vp), &fvalue );
								stringVal	= UString::number( fvalue );
								break;
			case JSOT_BOOL:
								bvalue	= (JSVAL_TO_BOOLEAN( (*vp) ) == JS_TRUE);
								if( bvalue )
									stringVal = "TRUE";
								else
									stringVal = "FALSE";
								break;
			case JSOT_STRING:
								stringVal	= JS_GetStringBytes( JS_ValueToString( cx, *vp ) );
								break;
			default:
			case JSOT_COUNT:
				break;
			}
			break;
		case JSOT_OBJECT:
			objectVal	= (void*)JS_GetPrivate( cx, JSVAL_TO_OBJECT( *vp ) );
			break;
		default:
		case JSOT_COUNT:
			break;
		}
		beenParsed[typeConvert] = true;
	}
}
